import numpy as np
import pandas as pd

from utils.common import divide
from .window import *
from collections import defaultdict


class BasicFeature:

    def __init__(self, applist_data_df: pd.DataFrame, appinfo_data_df: pd.DataFrame, cutwin: TimeWindow = TimeWindow([3, 7, 15, 30, 60, 90, 180, 360])):
        """
        app_list: 用户app原始安装列表数据
        data: 外部数据关联数据表，如竞品列表
        cutwin: TimeWindow类，用于生成时间窗口，拆分原始数据
        """
        self.applist_data_df = applist_data_df
        self.appinfo_data_df = appinfo_data_df
        self.cutwin = cutwin
        self.get_meta()

    def get_meta(self):
        self.meta = {
            'basic_count': '【基础计数】1~2个TAG组合+count',
            'basic_discount': '【类别计数】各TAG的TAG_VALUE的distinctCount',
            'basic_toptag': '【TOP VALUE】各TAG次数TOP的TAG_VALUE取值(类别型)',
            'basic_toptag_count': '【TOP COUNT】各TAG次数最多的次数+count',
            'basic_toptag_ratio': '各TAG次数最多的+count+占比',
            'basic_tag_ratio': '各TAG的count+占比',
            'basic_tag_poly': '1~2个TAG+可聚合实体（金额）+sum/avg/max/min/med',
            'basic_tagcnt_cyclical': '1~2个TAG组合+count+环比变动率',
            'basic_tagsum_cyclical': '各TAG+可聚合实体+sum+环比变动率',
            'basic_upgrade_count': 'app升级次数',
            'basic_oneday': '用户单日下载app最大值、比例',
            'basic_get_days_from_now': '用户下载app距今天数'
        }
        self.func_map = {
            'basic_count': self.basic_count,
            'basic_discount': self.basic_discount,
            'basic_toptag': self.basic_toptag,
            'basic_toptag_count': self.basic_toptag_count,
            'basic_toptag_ratio': self.basic_toptag_ratio,
            'basic_tag_ratio': self.basic_tag_ratio,
            'basic_tag_poly': self.basic_tag_poly,
            'basic_tagcnt_cyclical': self.basic_tagcnt_cyclical,
            'basic_tagsum_cyclical': self.basic_tagsum_cyclical,
            'basic_upgrade_count': self.basic_upgrade_count,
            'basic_oneday': self.basic_oneday,
            'basic_get_days_from_now': self.basic_get_days_from_now
        }

    def __call__(self, tags: list, tags_num: list, fuc_list: list, win=True):
        """组装函数"""
        if win:
            return self.create_winfeatures(func_list=fuc_list, tags=tags, tags_num=tags_num)
        else:
            return self.create_feature(func_list=fuc_list, tags=tags, tags_num=tags_num)

    def basic_count(self, data: pd.DataFrame, tags: list, **kwargs) -> dict:
        """【基础计数】1~2个TAG组合+count"""
        feature_dict = defaultdict(int)
        for i in tags:
            feature_dict[self.join_str([i, 'cnt'])] = data[i].count()
        return feature_dict

    def basic_discount(self, data: pd.DataFrame, tags: list, **kwargs) -> dict:
        """【类别计数】各TAG的TAG_VALUE的distinctCount"""
        feature_dict = defaultdict(int)
        for i in tags:
            feature_dict[self.join_str([i, 'discnt'])] = data[i].nunique()
            tag_unique = self.appinfo_data_df[i].unique()
            for j in tag_unique:
                feature_dict[self.join_str([i, j, 'discnt'])] = len(data[data[i] == j])
        return feature_dict

    def basic_toptag(self, data: pd.DataFrame, tags: list, **kwargs) -> dict:
        """【TOP VALUE】各TAG次数TOP的TAG_VALUE取值(类别型)"""
        feature_dict = defaultdict(str)
        for i in tags:
            if not data[i].empty:
                feature_dict[self.join_str([i, 'toptag', 'name'])] = data[i].value_counts().idxmax()
            else:
                feature_dict[self.join_str([i, 'toptag', 'name'])] = ''
        return feature_dict

    def basic_toptag_count(self, data: pd.DataFrame, tags: list, **kwargs) -> dict:
        """【TOP COUNT】各TAG次数最多的次数+count"""
        feature_dict = defaultdict(int)
        for i in tags:
            if not data[i].empty:
                feature_dict[self.join_str([i, 'topcnt'])] = data[i].value_counts().iloc[0]
            else:
                feature_dict[self.join_str([i, 'topcnt'])] = 0
        return feature_dict

    def basic_toptag_ratio(self, data: pd.DataFrame, tags: list, **kwargs) -> dict:
        """各TAG次数最多的+count+占比"""
        feature_dict = defaultdict(int)
        for i in tags:
            if not data[i].empty:
                feature_dict[self.join_str([i, 'topratiofp'])] = divide(data[i].value_counts().iloc[0], data[i].count())
            else:
                feature_dict[self.join_str([i, 'topratiofp'])] = np.nan
        return feature_dict

    def basic_tag_ratio(self, data: pd.DataFrame, tags: list, **kwargs) -> dict:
        """各TAG的count+占比"""
        feature_dict = defaultdict(int)
        for i in tags:
            feature_dict[self.join_str([i, 'ratio'])] = divide(data[i].count(), len(data))
        return feature_dict

    def basic_tag_poly(self, data: pd.DataFrame, tags: list, tags_num: list) -> dict:
        """1~2个TAG+可聚合实体（金额）+sum/avg/max/min/med"""
        feature_dict = defaultdict(int)
        for i in tags_num:
            feature_dict[self.join_str([i, 'sum'])] = data[i].sum()
            feature_dict[self.join_str([i, 'max'])] = data[i].max()
            feature_dict[self.join_str([i, 'min'])] = data[i].min()
            feature_dict[self.join_str([i, 'med'])] = data[i].median()
            feature_dict[self.join_str([i, 'avg'])] = divide(data[i].sum(), data[i].count())

        # 类别型tag和聚合型tag交叉
        zu_tags = []
        for tag in tags:
            if tag in ['package_name', 'app_name']:
                continue
            for tn in tags_num:
                zu_tags.append((tag, tn))

        for tag_item in zu_tags:
            tag_unique = self.appinfo_data_df[tag_item[0]].unique()
            for i in tag_unique:
                feature_dict[self.join_str([tag_item[0], str(i), tag_item[1], 'sum'])] = data.loc[data[tag_item[0]] == i, tag_item[1]].sum()
                feature_dict[self.join_str([tag_item[0], str(i), tag_item[1], 'avg'])] = divide(data.loc[data[tag_item[0]] == i, tag_item[1]].sum(), len(data[data[tag_item[0]] == i]))
                feature_dict[self.join_str([tag_item[0], str(i), tag_item[1], 'max'])] = data.loc[data[tag_item[0]] == i, tag_item[1]].max()
                feature_dict[self.join_str([tag_item[0], str(i), tag_item[1], 'min'])] = data.loc[data[tag_item[0]] == i, tag_item[1]].min()
                feature_dict[self.join_str([tag_item[0], str(i), tag_item[1], 'med'])] = data.loc[data[tag_item[0]] == i, tag_item[1]].median()
        return feature_dict

    def basic_tagcnt_cyclical(self, data: pd.DataFrame, tags: list, tags_num: list, cycrate=30, **kwargs) -> dict:
        """1~2个TAG组合+count+环比变动率"""
        feature_dict = defaultdict(int)
        df1, df2 = self.cutwin.get_cycdata(data, cycrate)
        for i in tags:
            if not df1.empty:
                tmp_df1_cnt = len(df1[i])
            else:
                tmp_df1_cnt = 0
            if not df2.empty:
                tmp_df2_cnt = len(df2[i])
            else:
                tmp_df2_cnt = 0
            feature_dict[self.join_str([i, 'cycrate'])] = divide(tmp_df1_cnt, tmp_df2_cnt)
        return feature_dict

    def basic_tagsum_cyclical(self, data: pd.DataFrame, tags: list, tags_num: list, cycrate=30, **kwargs) -> dict:
        """各TAG+可聚合实体+sum+环比变动率"""
        feature_dict = defaultdict(int)
        df1, df2 = self.cutwin.get_cycdata(data, cycrate)
        for i in tags_num:
            if not df1.empty:
                tmp_df1_sum = df1[i].sum()
            else:
                tmp_df1_sum = 0
            if not df2.empty:
                tmp_df2_sum = df2[i].sum()
            else:
                tmp_df2_sum = 0
            feature_dict[self.join_str([i, 'cycrate'])] = divide(tmp_df1_sum, tmp_df2_sum)
        return feature_dict

    def basic_upgrade_count(self, data: pd.DataFrame, tags: list, tags_num: list, **kwargs) -> dict:
        """app升级次数"""
        feature_dict = defaultdict(int)
        feature_dict[self.join_str(['ugd', 'cnt'])] = len(data[data['inst_time'] != data['updt_time']])
        return feature_dict

    def basic_oneday(self, data: pd.DataFrame, tags: list, tags_num: list, **kwargs) -> dict:
        feature_dict = defaultdict(int)
        for i in tags:
            tag_unique = self.appinfo_data_df[i].unique()
            for j in tag_unique:
                df = data[data[i] == j]
                if not df.empty:
                    download_oneday = (
                        df.groupby("inst_date")["app"]
                        .count()
                        .reset_index()
                        .sort_values("app", ascending=False)
                        .reset_index(drop=True)
                    )
                    feature_dict[self.join_str([i, j, "downlmax", "cnt"])] = download_oneday["app"].values[0]
                    feature_dict[self.join_str([i, j, "downlmax", "bydayrate"])] = download_oneday["app"].values[0] / df.shape[0]
                else:
                    feature_dict[self.join_str([i, j, "downlmax", "cnt"])] = 0
                    feature_dict[self.join_str([i, j, "downlmax", "bydayrate"])] = np.nan
        return feature_dict

    def basic_get_days_from_now(self, data: pd.DataFrame, tags: list, tags_num: list, **kwargs):
        feature_dict = defaultdict(int)
        for i in tags:
            tag_unique = self.appinfo_data_df[i].unique()
            for j in tag_unique:
                df = data[data[i] == j]
                feature_dict[self.join_str([i, j, "maxinst", "dayfn"])] = df["up2in_time_diff_days"].max()
                feature_dict[self.join_str([i, j, "mininst", "dayfn"])] = df["up2in_time_diff_days"].min()
                feature_dict[self.join_str([i, j, "suminst", "dayfn"])] = df["up2in_time_diff_days"].sum()
                feature_dict[self.join_str([i, j, "meaninst", "dayfn"])] = df["up2in_time_diff_days"].mean()
        return feature_dict

    def join_str(self, tag_list):
        return '_'.join(tag_list)

    def create_feature(self, func_list: list, tags: list, tags_num: list, *args, **kwargs):
        """使用全量数据生成特征函数，不会使用时间窗口对总数据进行拆分"""
        dfs = []
        data = pd.merge(self.appinfo_data_df, self.applist_data_df, left_on='package_name', right_on='package',how='right')
        for func in func_list:
            features = self.func_map[func](data=data, tags=tags, tags_num=tags_num)
            dfs.extend(features)
        return self.cutwin.merge_dfs(dfs)

    def create_winfeatures(self, func_list: list, tags: list, tags_num: list, *args, **kwargs):
        """使用时间窗口对总数据进行拆分，拆分后的数据生成特征函数"""
        dfs = []
        t_dict = dict(zip(self.cutwin.window_names, self.cutwin.window_days))
        for name, applist_data_df_part in self.cutwin.iter(self.applist_data_df):
            data = pd.merge(self.appinfo_data_df, applist_data_df_part, left_on='package_name', right_on='package',how='right')
            for func in func_list:
                if 'cyclical' not in func:
                    features = self.func_map[func](data=data, tags=tags, tags_num=tags_num)
                    if features:
                        features_new = [{f"{key}_{name}": features[key]} for key in features]
                        dfs.extend(features_new)
                else:
                    features = self.func_map[func](data=data, tags=tags, tags_num=tags_num, cycrate=t_dict[name])
                    if features:
                        features_new = [{f"{key}_{name}": features[key]} for key in features]
                        dfs.extend(features_new)

        return self.cutwin.merge_dfs(dfs)
